/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.cdi.test;

import org.activiti.cdi.BusinessProcess;
import org.activiti.cdi.impl.util.ProgrammaticBeanLookup;
import org.activiti.cdi.test.util.ProcessEngineLookupForTestsuite;
import org.activiti.engine.*;
import org.activiti.engine.impl.ProcessEngineImpl;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.jobexecutor.JobExecutor;
import org.activiti.engine.test.ActivitiRule;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.enterprise.inject.spi.BeanManager;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;

/**
 * Abstract base class for executing activiti-cdi tests in a Java SE
 * environment, using Weld-SE.
 *
 * @author Daniel Meyer
 */
@RunWith(Arquillian.class)
public abstract class CdiActivitiTestCase {

    @Rule
    public ActivitiRule activitiRule = new ActivitiRule(getBeanInstance(ProcessEngine.class));
    protected Logger logger = LoggerFactory.getLogger(getClass().getName());
    protected BeanManager beanManager;
    protected ProcessEngine processEngine;
    protected FormService formService;
    protected HistoryService historyService;
    protected IdentityService identityService;
    protected ManagementService managementService;
    protected RepositoryService repositoryService;
    protected RuntimeService runtimeService;
    protected TaskService taskService;
    protected ProcessEngineConfigurationImpl processEngineConfiguration;

    @Deployment
    public static JavaArchive createDeployment() {

        return ShrinkWrap.create(JavaArchive.class)
                .addPackages(true, "org.activiti.cdi")
                .addAsManifestResource("META-INF/beans.xml", "beans.xml");
    }

    @Before
    public void setUp() throws Exception {

        beanManager = ProgrammaticBeanLookup.lookup(BeanManager.class);
        processEngine = ProgrammaticBeanLookup.lookup(ProcessEngine.class);
        processEngineConfiguration = ((ProcessEngineImpl) ProcessEngineLookupForTestsuite.processEngine).getProcessEngineConfiguration();
        activitiRule.setProcessEngineConfiguration(processEngineConfiguration);
        formService = processEngine.getFormService();
        historyService = processEngine.getHistoryService();
        identityService = processEngine.getIdentityService();
        managementService = processEngine.getManagementService();
        repositoryService = processEngine.getRepositoryService();
        runtimeService = processEngine.getRuntimeService();
        taskService = processEngine.getTaskService();
    }

    protected void endConversationAndBeginNew(String processInstanceId) {
        getBeanInstance(BusinessProcess.class).associateExecutionById(processInstanceId);
    }

    protected <T> T getBeanInstance(Class<T> clazz) {
        return ProgrammaticBeanLookup.lookup(clazz);
    }

    protected Object getBeanInstance(String name) {
        return ProgrammaticBeanLookup.lookup(name);
    }

    //////////////////////// copied from AbstractActivitiTestcase

    public void waitForJobExecutorToProcessAllJobs(long maxMillisToWait, long intervalMillis) {
        JobExecutor jobExecutor = processEngineConfiguration.getJobExecutor();
        jobExecutor.start();

        try {
            Timer timer = new Timer();
            InteruptTask task = new InteruptTask(Thread.currentThread());
            timer.schedule(task, maxMillisToWait);
            boolean areJobsAvailable = true;
            try {
                while (areJobsAvailable && !task.isTimeLimitExceeded()) {
                    Thread.sleep(intervalMillis);
                    areJobsAvailable = areJobsAvailable();
                }
            } catch (InterruptedException e) {
            } finally {
                timer.cancel();
            }
            if (areJobsAvailable) {
                throw new ActivitiException("time limit of " + maxMillisToWait + " was exceeded");
            }

        } finally {
            jobExecutor.shutdown();
        }
    }

    public void waitForJobExecutorOnCondition(long maxMillisToWait, long intervalMillis, Callable<Boolean> condition) {
        JobExecutor jobExecutor = processEngineConfiguration.getJobExecutor();
        jobExecutor.start();

        try {
            Timer timer = new Timer();
            InteruptTask task = new InteruptTask(Thread.currentThread());
            timer.schedule(task, maxMillisToWait);
            boolean conditionIsViolated = true;
            try {
                while (conditionIsViolated) {
                    Thread.sleep(intervalMillis);
                    conditionIsViolated = !condition.call();
                }
            } catch (InterruptedException e) {
            } catch (Exception e) {
                throw new ActivitiException("Exception while waiting on condition: " + e.getMessage(), e);
            } finally {
                timer.cancel();
            }
            if (conditionIsViolated) {
                throw new ActivitiException("time limit of " + maxMillisToWait + " was exceeded");
            }

        } finally {
            jobExecutor.shutdown();
        }
    }

    public boolean areJobsAvailable() {
        return !managementService
                .createJobQuery()
                .executable()
                .list()
                .isEmpty();
    }

    private static class InteruptTask extends TimerTask {
        protected boolean timeLimitExceeded = false;
        protected Thread thread;

        public InteruptTask(Thread thread) {
            this.thread = thread;
        }

        public boolean isTimeLimitExceeded() {
            return timeLimitExceeded;
        }

        public void run() {
            timeLimitExceeded = true;
            thread.interrupt();
        }
    }
}
